//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include <errno.h>
#include "CBasicDirectory.h"
#define IncrementDllLockCount() do {} while(0)
#define DecrementDllLockCount() do {} while(0)
#include "_CKtfsDirectory.cpp"
#include "CBasicFile.h"
#include "CDirStrEnumerator.h"

static const WChar* wszIllegal = L"\"*\\<>/?:|";

static wchar_t *  __cdecl _wcspbrk(const wchar_t *s1, const wchar_t *s2)
{
    const wchar_t *scanp;
    int c, sc;

    while ((c = *s1++) != 0) {
        for (scanp = s2; (sc = *scanp++) != 0;) {
            if (sc == c) {
                return ((wchar_t *) (s1 - 1));
            }
        }
    }
    return 0;
}

CKtfsDirectory::CKtfsDirectory()
{
    m_wstrCurDir = NULL;
    AddRef();
}

CKtfsDirectory::CKtfsDirectory(WString wszDir, FileAccessMode faMode)
{
    WChar * wcsCurDir;
    wcsCurDir = (WChar *)malloc(sizeof(WChar) * (wcslen(wszDir) + 1));
    assert(wcsCurDir);
    wcscpy(wcsCurDir, wszDir);
    m_wstrCurDir = wcsCurDir;
    m_eFMode = faMode;
    AddRef();
}

CKtfsDirectory::~CKtfsDirectory()
{
    if (NULL != m_wstrCurDir) {
        free((void*)(const wchar_t*)m_wstrCurDir);
    }
}

ECode CKtfsDirectory::GetNameStat(
                WChar *wcsName, Boolean *bIsExist, Boolean *bIsDir)
{
    UInt32 attr = IO_GetFileAttributes(wcsName);
    if (attr == 0xffffffff) {
        *bIsExist = FALSE;
        return S_FALSE;
    }
    else {
        *bIsExist = TRUE;
    }
    if (attr & IO_FA_DIRECTORY) {
        *bIsDir = TRUE;
    }
    else {
        *bIsDir = FALSE;
    }
    return NOERROR;
}

ECode CKtfsDirectory::CombineName(
                WString name, WChar *fullname)
{
    assert(NULL != m_wstrCurDir);
    WChar *wcsName = fullname;
    wcscat(wcsName, (const WChar *)m_wstrCurDir);
    wcscat(wcsName, L"\\");
    wcscat(wcsName, (const WChar *)name);

    return NOERROR;
}

ECode CKtfsDirectory::Sync()
{
    return E_NOT_IMPLEMENTED;
}

ECode CKtfsDirectory::GetType(
    /* [out] */ FileType * pType)
{
    if (!pType) {
        return E_INVALID_ARGUMENT;
    }

    *pType = FileType_Directory;
    return NOERROR;
}

ECode CKtfsDirectory::GetAccessTime(
                /* [out] */ Int32 * pTime)
{
    if (!pTime) {
        return E_INVALID_ARGUMENT;
    }

    *pTime = m_iAccessTime;
    return NOERROR;
}

ECode CKtfsDirectory::SetAccessTime(
                /* [in] */ Int32 time)
{
    m_iAccessTime = time;
    return NOERROR;
}

ECode CKtfsDirectory::GetModificationTime(
                /* [out] */ Int32 * pTime)
{
    if (!pTime) {
        return E_INVALID_ARGUMENT;
    }

    *pTime = m_iModificationTime;
    return NOERROR;
}

ECode CKtfsDirectory::SetModificationTime(
                /* [in] */ Int32 time)
{
    m_iModificationTime = time;
    return NOERROR;
}

ECode CKtfsDirectory::GetCreateTime(
                /* [out] */ Int32 * pTime)
{
    if (!pTime) {
        return E_INVALID_ARGUMENT;
    }

    *pTime = m_iCreateTime;
    return NOERROR;
}

ECode CKtfsDirectory::CheckAccessMode(
                /* [in] */ FileAccessMode mode)
{
    if (m_eFMode == mode) {
        return NOERROR;
    }
    else {
        return S_FALSE;
    }
}

ECode CKtfsDirectory::GetAccessMode(
                /* [out] */ FileAccessMode * pMode)
{
    if (!pMode) {
        return E_INVALID_ARGUMENT;
    }

    *pMode = m_eFMode;
    return NOERROR;
}

ECode CKtfsDirectory::SetAccessMode(
                /* [in] */ FileAccessMode mode)
{
    const wchar_t *wcsName = m_wstrCurDir;
    UInt32 attr = IO_GetFileAttributes (wcsName);
    if (mode == FileAccessMode_Read) {
        attr |= IO_FA_READONLY;
    }
    else {
        attr &= ~IO_FA_READONLY;
    }
    IO_SetFileAttributes (wcsName, attr);
    m_eFMode = mode;
    return NOERROR;
}

ECode CKtfsDirectory::GetShareMode(
                /* [out] */ FileShareMode * pMode)
{
    return E_NOT_IMPLEMENTED;
}

ECode CKtfsDirectory::SetShareMode(
                /* [in] */ FileShareMode mode)
{
    return E_NOT_IMPLEMENTED;
}

ECode CKtfsDirectory::Lookup(
                /* [in] */ WString wstrPath,
                /* [out] */ IBasicFile * * piBasicFile)
{
    if (wcschr((const wchar_t *)wstrPath, '\\') ||
        wcschr((const wchar_t *)wstrPath, '/')) {
        return E_INVALID_ARGUMENT;
    }
    if (piBasicFile == NULL) {
        return E_INVALID_ARGUMENT;
    }

    Boolean bIsExist = FALSE;
    Boolean bIsDir = FALSE;
    ECode ec = NOERROR;
    WChar *wcsName = new WChar[_MAX_PATH];
    if (wcsName == NULL) {
        return E_OUT_OF_MEMORY;
    }
    memset(wcsName, 0, sizeof(WChar[_MAX_PATH]));
    CombineName(wstrPath, wcsName);
    GetNameStat(wcsName, &bIsExist, &bIsDir);
    if (bIsExist && bIsDir) {
        CKtfsDirectory *pBasicDirectory = new CKtfsDirectory((WString)wcsName);
        if (pBasicDirectory == NULL) {
            ec = E_OUT_OF_MEMORY;
            goto L_EXIT;
        }
        pBasicDirectory->AddRef();
        *piBasicFile = IBasicFile::Probe((_IInterface*)pBasicDirectory);
        if (!(*piBasicFile)) {
            ec = E_NO_INTERFACE;
            goto L_EXIT;
        }
        (*piBasicFile)->AddRef();
        pBasicDirectory->Release();
        goto L_EXIT;
    }
    else if (!bIsExist) {
        //dir or file does not exist
        *piBasicFile = NULL;
        ec = E_FAIL;
        goto L_EXIT;
    }
    else if (bIsExist && !bIsDir) {
        CKtfsFile *pBasicFile = new CKtfsFile(wcsName, TRUE, FALSE);
        if (pBasicFile == NULL) {
            ec = E_OUT_OF_MEMORY;
            goto L_EXIT;
        }
        pBasicFile->AddRef();
        *piBasicFile = pBasicFile;
        ec = NOERROR;
        goto L_EXIT;
    }
    ec = E_FAIL;

L_EXIT:
    delete[] wcsName;
    return ec;
}

ECode CKtfsDirectory::CreateFile(
                /* [in] */ WString wstrPath,
                /* [in] */ FileAccessMode cmode,
                /* [in] */ Boolean bExclusive,
                /* [out] */ IBasicFile * * piBasicFile)
{
    if (NULL == wstrPath) {
        return E_INVALID_ARGUMENT;
    }
    if (_wcspbrk((const wchar_t *)wstrPath, wszIllegal) != NULL) {
        return E_INVALID_ARGUMENT;
    }

    CKtfsFile *pBasicFile = NULL;
    ECode ec = NOERROR;
    WChar *wcsName = new WChar[_MAX_PATH];
    if (wcsName == NULL) {
        return E_OUT_OF_MEMORY;
    }
    memset(wcsName, 0, sizeof(WChar[_MAX_PATH]));
    CombineName(wstrPath, wcsName);

    UInt32 attr = IO_GetFileAttributes (wcsName);
    if (attr == 0xffffffff) {  //file does not exist
        pBasicFile = new CKtfsFile(wcsName, FALSE, TRUE);
        if (pBasicFile == NULL) {
            ec = E_OUT_OF_MEMORY;
            goto L_EXIT;
        }
    }
    else if ((cmode & FileAccessMode_Write) ||
        (cmode & FileAccessMode_ReadWrite)) {
        //file exist and open with write access
        if (bExclusive == FALSE) {
            pBasicFile = new CKtfsFile(wcsName, TRUE, TRUE);
            if (pBasicFile == NULL) {
                ec = E_OUT_OF_MEMORY;
                goto L_EXIT;
            }
        }
        else {
            *piBasicFile = NULL;
            ec = E_FAIL;
            goto L_EXIT;
        }
    }
    else {
        //file exist and open with read access
        if (bExclusive == FALSE) {
            pBasicFile = new CKtfsFile(wcsName, TRUE, FALSE);
            if (pBasicFile == NULL) {
                ec = E_OUT_OF_MEMORY;
                goto L_EXIT;
            }
        }
        else {
            *piBasicFile = NULL;
            ec = E_FAIL;
            goto L_EXIT;
        }
    }
    pBasicFile->AddRef();
    pBasicFile->SetAccessMode(cmode);
    *piBasicFile = (IBasicFile *)pBasicFile;
    ec = NOERROR;

L_EXIT:
    delete[] wcsName;
    return ec;
}

ECode CKtfsDirectory::DeleteFile(
                /* [in] */ WString wstrPath)
{
    if (NULL == wstrPath) {
        return E_INVALID_ARGUMENT;
    }

    Boolean bIsExist = FALSE;
    Boolean bIsDir = FALSE;
    Boolean ret = FALSE;
    ECode ec = NOERROR;
    WChar *wcsName = new WChar[_MAX_PATH];
    if (wcsName == NULL) {
        return E_OUT_OF_MEMORY;
    }
    memset(wcsName, 0, sizeof(WChar[_MAX_PATH]));
    CombineName(wstrPath, wcsName);
    GetNameStat(wcsName, &bIsExist, &bIsDir);
    if (!bIsExist) {
        //file doesnt exist
        ec = ECODE_FROM_ERRNO(ENOENT);
        goto L_EXIT;
    }

    ret = IO_DeleteFile (wcsName);
    if (!ret) {
        ec = E_FAIL;
        goto L_EXIT;
    }
    ec = NOERROR;

L_EXIT:
    delete [] wcsName;
    return ec;
}

ECode CKtfsDirectory::Rename(
                /* [in] */ WString wstrSrcPath,
                /* [in] */ IBasicDirectory * pNewDir,
                /* [in] */ WString wstrDestPath)
{
    if (NULL == wstrSrcPath || NULL == wstrDestPath) {
        return E_INVALID_ARGUMENT;
    }

    Boolean bOldNameExist, bOldNameIsDir;
    Boolean bNewNameExist, bNewNameIsDir;
    ECode ec = NOERROR;
    Boolean ret = FALSE;
    WChar *wcsOldName = new WChar[_MAX_PATH];
    if (wcsOldName == NULL) {
        return E_OUT_OF_MEMORY;
    }
    memset(wcsOldName, 0, sizeof(WChar[_MAX_PATH]));
    WChar *wcsNewName = new WChar[_MAX_PATH];
    if (wcsNewName == NULL) {
        delete[] wcsOldName;
        return E_OUT_OF_MEMORY;
    }
    memset(wcsNewName, 0, sizeof(WChar[_MAX_PATH]));
    CombineName(wstrSrcPath, wcsOldName);
    ((CKtfsDirectory *)pNewDir)->CombineName(wstrDestPath, wcsNewName);

    GetNameStat(wcsOldName, &bOldNameExist, &bOldNameIsDir);
    GetNameStat(wcsNewName, &bNewNameExist, &bNewNameIsDir);
    if (!bOldNameExist) {
        //oldname doesnt exist
        ec = ECODE_FROM_ERRNO(ENOENT);
        goto L_EXIT;
    }
    else if (bOldNameExist && bOldNameIsDir &&
        bNewNameExist && !bNewNameIsDir) {
        //oldname is dir and newname is file that has already exist
        ec = ECODE_FROM_ERRNO(ENOTDIR);
        goto L_EXIT;
    }
    else if (bOldNameExist && !bOldNameIsDir &&
        bNewNameExist && bNewNameIsDir) {
        //oldname is file and newname is dir that has already exist
        ec = ECODE_FROM_ERRNO(EISDIR);
        goto L_EXIT;
    }
    else if (bNewNameExist) {
        //newname exist
        ec = ECODE_FROM_ERRNO(EEXIST);
        goto L_EXIT;
    }
    ret = IO_MoveFile (wcsOldName, wcsNewName);
    if (!ret) {
        ec = E_FAIL;
        goto L_EXIT;
    }
    ec = NOERROR;

L_EXIT:
    delete[] wcsOldName;
    delete[] wcsNewName;
    return ec;
}

ECode CKtfsDirectory::CreateDirectory(
                /* [in] */ WString wstrPath,
                /* [in] */ FileAccessMode mode)
{
    if (NULL == wstrPath) return E_INVALID_ARGUMENT;

    if (_wcspbrk((const wchar_t *)wstrPath, wszIllegal) != NULL) {
        return ECODE_FROM_ERRNO(EINVAL);
    }

    Boolean bIsExist = FALSE;
    Boolean bIsDir = FALSE;
    Boolean ret = FALSE;
    ECode ec = NOERROR;
    WChar *wcsName = new WChar[_MAX_PATH];
    if (wcsName == NULL) {
        return E_OUT_OF_MEMORY;
    }
    memset(wcsName, 0, sizeof(WChar[_MAX_PATH]));
    CombineName(wstrPath, wcsName);
    GetNameStat(wcsName, &bIsExist, &bIsDir);
    if (bIsExist && bIsDir) {
        ec = ECODE_FROM_ERRNO(EEXIST);
        goto L_EXIT;
    }

    ret = IO_CreateDirectory(wcsName);
    if (!ret) {
        ec = E_FAIL;
        goto L_EXIT;
    }
    if (mode & FileAccessMode_Read) {
        UInt32 attr = IO_GetFileAttributes (wcsName);
        attr |= IO_FA_READONLY;
        IO_SetFileAttributes (wcsName, attr);
    }
    ec = NOERROR;

L_EXIT:
    delete[] wcsName;
    return ec;
}

ECode CKtfsDirectory::DeleteDirectory(
                /* [in] */ WString wstrPath)
{
    if (NULL == wstrPath) return E_INVALID_ARGUMENT;

    Boolean bIsExist = FALSE;
    Boolean bIsDir = FALSE;
    Boolean ret = FALSE;
    ECode ec = NOERROR;
    WChar *wcsName = new WChar[_MAX_PATH];
    if (wcsName == NULL) {
        return E_OUT_OF_MEMORY;
    }
    memset(wcsName, 0, sizeof(WChar[_MAX_PATH]));
    CombineName(wstrPath, wcsName);
    GetNameStat(wcsName, &bIsExist, &bIsDir);
    if (!bIsExist) {
        ec = ECODE_FROM_ERRNO(ENOENT);
        goto L_EXIT;
    }

    ret = IO_RemoveDirectory (wcsName);
    if (!ret) {
        ec = E_FAIL;
        goto L_EXIT;
    }
    ec = NOERROR;

L_EXIT:
    delete[] wcsName;
    return ec;
}

ECode CKtfsDirectory::GetDirEntries(
                /* [out] */ IWStringEnumerator * * piEntryNameEnumerator)
{
    if (!piEntryNameEnumerator) return E_INVALID_ARGUMENT;
    CKtfsDirStrEnumerator *pDirStrEnum = NULL;
    ECode ec = NOERROR;
    WChar *wcsPath = new WChar[_MAX_PATH];
    if (wcsPath == NULL) {
        return E_OUT_OF_MEMORY;
    }
    memset(wcsPath, 0, sizeof(WChar[_MAX_PATH]));
    wcscat(wcsPath, (const wchar_t *)m_wstrCurDir);
    wcscat(wcsPath, L"\\*.*");
    pDirStrEnum = new CKtfsDirStrEnumerator(wcsPath);
    if (!pDirStrEnum) {
        ec = E_OUT_OF_MEMORY;
        goto L_EXIT;
    }
    pDirStrEnum->AddRef();
    *piEntryNameEnumerator = pDirStrEnum;
    ec = NOERROR;

L_EXIT:
    delete[] wcsPath;
    return ec;
}

ECode CKtfsDirectory::AddFileMonitor(
                /* [out] */ IEvent** ppEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CKtfsDirectory::RemoveFileMonitor(
                /* [in] */ IEvent * pEvent)
{
    return E_NOT_IMPLEMENTED;
}
